package top.lightHearted.bean;

import com.google.gson.JsonObject;
import org.kurento.client.Continuation;
import org.kurento.client.IceCandidate;
import org.kurento.client.MediaPipeline;
import org.kurento.client.WebRtcEndpoint;
import org.kurento.jsonrpc.JsonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * UserSession class
 *
 * @author gpf
 * @date 2018/04/16
 */
public class UserSession implements Closeable {

    private static final Logger logger = LoggerFactory.getLogger(UserSession.class);

    private String name;

    private WebSocketSession session;

    private MediaPipeline mediaPipeline;

    private String roomName;

    private WebRtcEndpoint outgoingMedia;

    private ConcurrentMap<String, WebRtcEndpoint> incomingMedia = new ConcurrentHashMap<>();

    public UserSession(String name, WebSocketSession session, MediaPipeline mediaPipeline, String roomName) {
        this.name = name;
        this.session = session;
        this.mediaPipeline = mediaPipeline;
        this.roomName = roomName;

        this.outgoingMedia = new WebRtcEndpoint.Builder(mediaPipeline).build();
        this.outgoingMedia.addIceCandidateFoundListener(iceCandidateFoundEvent -> {
            JsonObject response = new JsonObject();
            response.addProperty("id", "iceCandidate");
            response.addProperty("name", name);
            response.add("candidate", JsonUtils.toJsonObject(iceCandidateFoundEvent.getCandidate()));
            try {
                synchronized (session){
                    session.sendMessage(new TextMessage(response.toString()));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

    }

    public void receiveVideoFrom(UserSession sender, String sdpOffer) throws IOException {
        String isSdpAnswer = this.getEndpointForUser(sender).processAnswer(sdpOffer);
        final JsonObject answer = new JsonObject();
        answer.addProperty("id", "receiveVideoAnswer");
        answer.addProperty("name", sender.getName());
        answer.addProperty("sdpAnswer", isSdpAnswer);
        this.sendMessage(answer);
        this.getEndpointForUser(sender).gatherCandidates();
    }

    private WebRtcEndpoint getEndpointForUser(final UserSession sender) {
        if(sender.getName().equals(name)){
            return outgoingMedia;
        }
        WebRtcEndpoint incoming = incomingMedia.get(sender.getName());
        if(incoming == null){
            incoming = new WebRtcEndpoint.Builder(mediaPipeline).build();
            incoming.addIceCandidateFoundListener(iceCandidateFoundEvent -> {
                JsonObject response = new JsonObject();
                response.addProperty("id", "iceCandidate");
                response.addProperty("name", name);
                response.add("candidate", JsonUtils.toJsonObject(iceCandidateFoundEvent.getCandidate()));
                try {
                    synchronized (session){
                        session.sendMessage(new TextMessage(response.toString()));
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        }
        incomingMedia.putIfAbsent(sender.getName(), incoming);
        return incoming;
    }

    public void addCandidate(IceCandidate iceCandidate, String name){
        if(this.name.compareTo(name) == 0){
            outgoingMedia.addIceCandidate(iceCandidate);
        }else{
            incomingMedia.get(name).addIceCandidate(iceCandidate);
        }
    }

    @Override
    public void close() {

    }

    public void sendMessage(JsonObject message) throws IOException {
        synchronized (session){
            session.sendMessage(new TextMessage(message.toString()));
        }
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public WebSocketSession getSession() {
        return session;
    }

    public void setSession(WebSocketSession session) {
        this.session = session;
    }

    public MediaPipeline getMediaPipeline() {
        return mediaPipeline;
    }

    public void setMediaPipeline(MediaPipeline mediaPipeline) {
        this.mediaPipeline = mediaPipeline;
    }

    public String getRoomName() {
        return roomName;
    }

    public void setRoomName(String roomName) {
        this.roomName = roomName;
    }

    public WebRtcEndpoint getOutgoingMedia() {
        return outgoingMedia;
    }

    public void setOutgoingMedia(WebRtcEndpoint outgoingMedia) {
        this.outgoingMedia = outgoingMedia;
    }

    public ConcurrentMap getIncomingMedia() {
        return incomingMedia;
    }

    public void setIncomingMedia(ConcurrentMap incomingMedia) {
        this.incomingMedia = incomingMedia;
    }

    /*
   * (non-Javadoc)
   *
   * @see java.lang.Object#equals(java.lang.Object)
   */
    @Override
    public boolean equals(Object obj) {

        if (this == obj) {
            return true;
        }
        if (obj == null || !(obj instanceof UserSession)) {
            return false;
        }
        UserSession other = (UserSession) obj;
        boolean eq = name.equals(other.name);
        eq &= roomName.equals(other.roomName);
        return eq;
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        int result = 1;
        result = 31 * result + name.hashCode();
        result = 31 * result + roomName.hashCode();
        return result;
    }

    public void cancelVideoFrom(String name) {
        final WebRtcEndpoint incoming = incomingMedia.remove(name);
        incoming.release(new Continuation<Void>() {
            @Override
            public void onSuccess(Void aVoid) throws Exception {

            }

            @Override
            public void onError(Throwable throwable) throws Exception {

            }
        });
    }
}
